﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip;

namespace LEE.Tools.IO
{
    public class ZipHelper
    {
        #region 压缩  

        /// <summary>   
        /// 递归压缩文件夹的内部方法   
        /// </summary>   
        /// <param name="folderToZip">要压缩的文件夹路径</param>   
        /// <param name="zipStream">压缩输出流</param>   
        /// <param name="parentFolderName">此文件夹的上级文件夹</param>   
        /// <returns></returns>   
        private static bool ZipDirectory(string folderToZip, ZipOutputStream zipStream, string parentFolderName)
        {
            var result = true;
            FileStream fs = null;
            var crc = new Crc32();
            try
            {
                var ent = new ZipEntry(Path.Combine(parentFolderName, Path.GetFileName(folderToZip) + "/"));
                zipStream.PutNextEntry(ent);
                zipStream.Flush();

                var files = Directory.GetFiles(folderToZip);
                foreach (var file in files)
                {
                    fs = File.OpenRead(file);

                    var buffer = new byte[fs.Length];
                    fs.Read(buffer, 0, buffer.Length);
                    ent = new ZipEntry(Path.Combine(parentFolderName, Path.GetFileName(folderToZip) + "/" + Path.GetFileName(file)))
                    {
                        DateTime = DateTime.Now,
                        Size = fs.Length
                    };

                    fs.Close();

                    crc.Reset();
                    crc.Update(buffer);

                    ent.Crc = crc.Value;
                    zipStream.PutNextEntry(ent);
                    zipStream.Write(buffer, 0, buffer.Length);
                }

            }
            catch
            {
                result = false;
            }
            finally
            {
                fs?.Dispose();
            }

            var folders = Directory.GetDirectories(folderToZip);
            return folders.All(folder => ZipDirectory(folder, zipStream, folderToZip)) && result;
        }

        private static void ZipDirectory(IEnumerable<ZipFolder> folders, ZipOutputStream zipStream, string rootPath = "")
        {
            var crc = new Crc32();
            foreach (var folder in folders)
            {
                var path = $"{rootPath}{folder.Name}/";
                var ent = new ZipEntry(path);
                zipStream.PutNextEntry(ent);
                zipStream.Flush();

                foreach (var file in folder.Files)
                {
                    ent = new ZipEntry($"{path}{ file.FileName}")
                    {
                        DateTime = DateTime.Now,
                        Size = file.FileData.Length
                    };
                    crc.Reset();
                    crc.Update(file.FileData);

                    ent.Crc = crc.Value;
                    zipStream.PutNextEntry(ent);
                    zipStream.Write(file.FileData, 0, file.FileData.Length);
                }

                var childFolders = folder.Folders ?? new List<ZipFolder>();

                ZipDirectory(childFolders, zipStream, path);
            }
        }

        public static byte[] ZipDirectory(ZipFolder folder)
        {
            return ZipDirectory(new List<ZipFolder> { folder });
        }

        public static byte[] ZipDirectory(List<ZipFolder> folders)
        {
            byte[] result;

            using (var ms = new MemoryStream())
            {
                using (var zipStream = new ZipOutputStream(ms))
                {
                    ZipDirectory(folders, zipStream);
                    zipStream.Finish();
                    result = new byte[ms.Length];
                    ms.Position = 0;
                    ms.Read(result, 0, result.Length);
                }
            }

            return result;
        }

        /// <summary>   
        /// 压缩文件夹    
        /// </summary>   
        /// <param name="folderToZip">要压缩的文件夹路径</param>   
        /// <param name="zipFile">压缩文件完整路径</param>   
        /// <param name="password">密码</param>   
        /// <returns>是否压缩成功</returns>   
        public static bool ZipDirectory(string folderToZip, string zipFile, string password = null)
        {
            if (!Directory.Exists(folderToZip))
                return false;

            using (var zipStream = new ZipOutputStream(File.Create(zipFile)))
            {
                zipStream.SetLevel(6);
                if (!string.IsNullOrEmpty(password)) zipStream.Password = password;

                return ZipDirectory(folderToZip, zipStream, "");
            }
        }

        /// <summary>   
        /// 压缩文件   
        /// </summary>   
        /// <param name="fileToZip">要压缩的文件全名</param>   
        /// <param name="zipFile">压缩后的文件名</param>   
        /// <param name="password">密码</param>   
        /// <returns>压缩结果</returns>   
        public static bool ZipFile(string fileToZip, string zipFile, string password = null)
        {
            var result = true;
            ZipOutputStream zipStream = null;
            FileStream fs = null;

            if (!File.Exists(fileToZip)) return false;

            try
            {
                fs = File.OpenRead(fileToZip);
                var buffer = new byte[fs.Length];
                fs.Read(buffer, 0, buffer.Length);
                fs.Close();

                fs = File.Create(zipFile);
                zipStream = new ZipOutputStream(fs);
                if (!string.IsNullOrEmpty(password)) zipStream.Password = password;
                var ent = new ZipEntry(Path.GetFileName(fileToZip));
                zipStream.PutNextEntry(ent);
                zipStream.SetLevel(6);

                zipStream.Write(buffer, 0, buffer.Length);

            }
            catch
            {
                result = false;
            }
            finally
            {

                zipStream?.Dispose();
                fs?.Dispose();
            }

            return result;
        }

        /// <summary>   
        /// 压缩文件或文件夹   
        /// </summary>   
        /// <param name="fileToZip">要压缩的路径</param>   
        /// <param name="zipFile">压缩后的文件名</param>   
        /// <param name="password">密码</param>   
        /// <returns>压缩结果</returns>   
        public static bool Zip(string fileToZip, string zipFile, string password = null)
        {
            var result = false;
            if (Directory.Exists(fileToZip))
                result = ZipDirectory(fileToZip, zipFile, password);
            else if (File.Exists(fileToZip))
                result = ZipFile(fileToZip, zipFile, password);

            return result;
        }

        #endregion

        #region 解压  

        /// <summary>   
        /// 解压功能(解压压缩文件到指定目录)   
        /// </summary>   
        /// <param name="fileToUnZip">待解压的文件</param>   
        /// <param name="zipFolder">指定解压目标目录</param>   
        /// <param name="password">密码</param>   
        /// <returns>解压结果</returns>   
        public static bool UnZip(string fileToUnZip, string zipFolder, string password = null)
        {
            if (!File.Exists(fileToUnZip))
            {
                return false;
            }
            using (var s = new ZipInputStream(File.OpenRead(fileToUnZip)))
            {
                ZipEntry theEntry;
                if (!string.IsNullOrEmpty(password))
                {
                    s.Password = password;
                }

                while ((theEntry = s.GetNextEntry()) != null)
                {
                    var fullPath = Path.Combine(zipFolder, theEntry.Name);
                    var directoryName = Path.GetDirectoryName(fullPath);
                    var fileName = Path.GetFileName(fullPath);
                    // create directory
                    if (!Directory.Exists(directoryName))
                    {
                        Directory.CreateDirectory(directoryName);
                    }

                    if (string.IsNullOrEmpty(fileName)) continue;

                    using (var streamWriter = File.Create(fullPath))
                    {
                        var data = new byte[2048];
                        while (true)
                        {
                            var size = s.Read(data, 0, data.Length);
                            if (size > 0)
                            {
                                streamWriter.Write(data, 0, size);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// 解压压缩文件流至内存
        /// </summary>
        /// <param name="stream">压缩文件流</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public static ZipFolder UnZipToMemory(Stream stream, string password = "")
        {
            var result = new ZipFolder();
            using (var zipStream = new ZipInputStream(stream))
            {
                ZipEntry theEntry;
                if (!string.IsNullOrEmpty(password))
                {
                    zipStream.Password = password;
                }

                while ((theEntry = zipStream.GetNextEntry()) != null)
                {
                    var tempFolder = result;
                    var names = theEntry.Name.Split('/');
                    int lastIndex;
                    if (theEntry.IsFile)
                    {
                        lastIndex = names.Length - 1;
                        for (var i = 0; i < lastIndex; i++)
                        {
                            var name = names[i];
                            if (string.IsNullOrWhiteSpace(name)) continue;
                            var parent = tempFolder;
                            tempFolder = parent.Folders.FirstOrDefault(x => x.Name == name);
                            if (tempFolder != null) continue;
                            tempFolder = new ZipFolder(name);
                            parent.Folders.Add(tempFolder);
                        }

                        var buffer = new byte[zipStream.Length];
                        zipStream.Read(buffer, 0, buffer.Length);
                        tempFolder.Files.Add(new ZipFile(names[lastIndex], buffer));
                    }
                    else if (theEntry.IsDirectory)
                    {
                        lastIndex = names.Length;
                        for (var i = 0; i < lastIndex; i++)
                        {
                            var name = names[i];
                            if (string.IsNullOrWhiteSpace(name)) continue;
                            var parent = tempFolder;
                            tempFolder = parent.Folders.FirstOrDefault(x => x.Name == name);
                            if (tempFolder != null) continue;
                            tempFolder = new ZipFolder(name);
                            parent.Folders.Add(tempFolder);
                        }
                    }
                }
            }
            return result;
        }

        #endregion
    }

    public class ZipFolder
    {
        public ZipFolder()
        {
            Files = new List<ZipFile>();
            Folders = new List<ZipFolder>();
        }

        public ZipFolder(string name) : this()
        {
            Name = name;
        }

        public string Name { get; set; }
        public List<ZipFile> Files { get; set; }
        public List<ZipFolder> Folders { get; set; }
    }

    public class ZipFile
    {
        public ZipFile()
        {
        }

        public ZipFile(string name, byte[] data)
        {
            FileName = name;
            FileData = data;
        }

        public string FileName { get; set; }
        public byte[] FileData { get; set; }
    }
}